package com.sf.proxy.custom.dynamic.jdkcustom.proxy;


import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

public class SFProxy {

    protected SFInvocationHandler h;

    public SFProxy(SFInvocationHandler sfInvocationHandler) {
        this.h = sfInvocationHandler;
    }

    /**
     *  换行回车
     */
    private static final String NEW_LINE = "\r\n";



    /**
     *  构建一个代理类，通过代码动态生成对象
     *  模板方法
     * @param loader
     * @param interfaces
     * @param h
     * @return
     */
    public static Object newProxyInstance(SFClassLoader loader,
                                          Class<?>[] interfaces,
                                          SFInvocationHandler h){
        try{
            //1.第一步构建 代码内容
            String codeStr = generCode(interfaces);
            //2.写入文件系统
            File file = write2File(codeStr);
            //3.读取编译成class 文件
            complieJavaFile(file);
            //4.加载class 文件到 jvm
            Constructor constructor = doLoadClass2JVM(loader, file);
            //5.返回新的 代理对象
            return constructor.newInstance(h);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static Constructor doLoadClass2JVM(SFClassLoader loader,File f) {
        try{
            Class proxyClass =  loader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(SFInvocationHandler.class);
            f.delete();
            return c;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static void complieJavaFile(File file) {
        try{
            //3、把生成的.java文件编译成.class文件
            //3.1 获取一个java 编译器
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            //3.2 获取一个标准的文件管理器
            StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);
            //3.3 从文件系统中获取对象
            Iterable iterable = manage.getJavaFileObjects(file);
            //创建诊断信息监听器, 用于诊断信息
            DiagnosticCollector<JavaFileObject> diagnosticListeners = new DiagnosticCollector<>();
            //3.4 编译器 编译这个对象
            JavaCompiler.CompilationTask task = compiler.getTask(null,manage,diagnosticListeners,null,null,iterable);
            Boolean call = task.call();
            System.out.println("编译:"+call);
            //输出诊断信息
            for(Diagnostic<? extends JavaFileObject> diagnostic : diagnosticListeners.getDiagnostics()) {
                //可以在此处自定义编译诊(错误)断信息的输出格式
                System.out.format("Error on line %d in %s%n",
                        diagnostic.getLineNumber(),
                        diagnostic.getSource().toUri());
            }
            manage.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private static File write2File(String codeStr) {
        try{
                //获取和 SFProxy 同级的 路径
            String path = SFProxy.class.getResource("").getPath();
            File f = new File(path + "$Proxy0.java");
            FileWriter fileWriter = new FileWriter(f);
            fileWriter.write(codeStr);
            fileWriter.flush();
            fileWriter.close();
            return f;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static String generCode(Class<?>[] interfaces) {
        //实现多个接口的情况
        if(!(interfaces != null && interfaces.length > 0)){throw new RuntimeException("interfaces is not be null or size=0");}
        //扩展实现多个接口
        String interfaceSimpleName = doGetInterfaceStr(interfaces);
        List<String> interfaceNameList = doGetInterfaceName(interfaces);

        StringBuffer sb = new StringBuffer();
        //设置 package 信息
        sb.append("package com.sf.proxy.custom.dynamic.jdkcustom.proxy;"+NEW_LINE);
        for(String fullName : interfaceNameList){
            sb.append("import "+fullName+";"+NEW_LINE);
        }
        sb.append("import java.lang.reflect.*;"+NEW_LINE);
        sb.append("public final class $Proxy0 extends SFProxy implements "+interfaceSimpleName);
        sb.append("{"+NEW_LINE);
        //构建构造方法
        sb.append("public $Proxy0(SFInvocationHandler invocationhandler)"+NEW_LINE);
        sb.append("{"+NEW_LINE);
        sb.append("super(invocationhandler);"+NEW_LINE);
        sb.append("}"+NEW_LINE);
        //获取 所有的方法
        List<ClaMethod> claMethods = doGetInterfaceMethod(interfaces);
        for (ClaMethod claMethod : claMethods){
            Method method = claMethod.getMethod();
            String interfaceName1 = claMethod.getInterfaceName();
            Parameter[] parameters = method.getParameters();
            StringBuffer className = new StringBuffer();
            StringBuffer paramValues = new StringBuffer();
            StringBuffer paramTypes = new StringBuffer();
            for (Parameter parameter : parameters){
                className.append(parameter.getClass().getSimpleName()+".class");
                //获取参数类型的集合 通过逗号分隔
                paramTypes.append(parameter.getType().getSimpleName()+".class"+",");
                //获取参数名的集合 通过逗号分隔
                paramValues.append(parameter.getName()+",");
            }
            sb.append("public final "+method.getReturnType().getSimpleName()+" "+method.getName()+"("+parserParameter(method.getParameters())+"){"+NEW_LINE);
            sb.append("try{"+NEW_LINE);
            sb.append("Method m = "+interfaceName1+".class.getMethod(\""+method.getName()+"\",new Class[]{"+paramTypes.toString().substring(0,paramTypes.length()-1)+"});"+NEW_LINE);
            if(method.getReturnType().getSimpleName().equals("void")){
                //判断是否有参数
                if(method.getParameters().length>0){
                    sb.append("super.h.invoke(this,m ,new Object[]{"+paramValues.toString().substring(0,paramValues.length()-1)+"});"+NEW_LINE);
                }else{
                    sb.append("super.h.invoke(this,m ,null);"+NEW_LINE);
                }
            }else{
                //判断是否有参数
                if(method.getParameters().length>0){
                    sb.append("return ("+method.getReturnType().getSimpleName()+")super.h.invoke(this,m ,new Object[]{"+paramValues.toString().substring(0,paramValues.length()-1)+"});"+NEW_LINE);
                }else{
                    sb.append("return ("+method.getReturnType().getSimpleName()+")super.h.invoke(this,m,null);"+NEW_LINE);
                }
            }
            sb.append("}catch(Error _ex){"+NEW_LINE);
            sb.append("}catch(Throwable throwable){"+NEW_LINE);
            sb.append("}"+NEW_LINE);
            if(!method.getReturnType().getSimpleName().equals("void")){
                sb.append("return null;"+NEW_LINE);
            }
            sb.append("}"+NEW_LINE);
        }
        sb.append("}");
        return sb.toString();
    }

    private static List<String> doGetInterfaceName(Class<?>[] interfaces) {
        List<String> list = new ArrayList<>();
        for(Class cls : interfaces){
            list.add(cls.getName());
        }
        return list;
    }

    private static List<ClaMethod> doGetInterfaceMethod(Class<?>[] interfaces) {
        List<ClaMethod> methods = new ArrayList<>();
        for(Class cls : interfaces){
            Method[] methods1 = cls.getMethods();
            for(Method method: methods1){
                ClaMethod claMethod = new ClaMethod();
                claMethod.setInterfaceName(cls.getSimpleName());
                claMethod.setMethod(method);
                methods.add(claMethod);
            }
        }
        return methods;
    }

    private static String doGetInterfaceStr(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        for(Class cls : interfaces){
            sb.append(cls.getSimpleName()+",");
        }
        String toString = sb.toString();
        String substring = toString.substring(0, toString.length() - 1);
        return substring;
    }

    /**
     *  处理参数
     * @param parameters
     * @return
     */
    public static String parserParameter(Parameter[] parameters){
        StringBuffer sb = new StringBuffer();
        if(parameters!=null &&parameters.length>0){
            for(Parameter parameter : parameters){
                String name = parameter.getName();
                String className = parameter.getType().getSimpleName();
                sb.append(className+" "+name+",");
            }
            //处理最后一个多余的 ,
            return sb.substring(0, sb.length() - 1);
        }
        return sb.toString();
    }



}
